/**
 * @Author Heinz Hölzer
 */

package scuts.core.macros;

#if macro

import scuts.macro.builder.ShortLambda;
import scuts.macro.builder.Comprehension;
import scuts.macro.Parse;
import scuts.macro.Print;
import scuts.macro.cache.ExprCache;

import haxe.macro.Context;
import haxe.macro.Expr;

private typedef CB = Comprehension;
#if Fdebug
private typedef EP = Print;
#end


using scuts.Core;
//using Lambda;
//using scuts.IteratorTools;

#end

class F 
{
  //{ region private
  
  #if macro
  
  #if (Ftimes)
  static var timesCallback = Context.onGenerate(function (t) cache.traceTimes("F.times"));
  static var cache = new ExprCache(true);
  #else
  static var cache = new ExprCache(false);
  #end

  private static function buildComprehension(compExprs:Array<Expr>, 
                      exprFactory:Array<Expr>->Expr, macroId:String):Expr 
  {
    
    var f = function () {
      return exprFactory(compExprs);
    };
    
    #if (debug && Fdebug)
    var printer = function () {
      return macroId + "(" + compExprs.mapA(function (e) return EP.exprStr(e)).join(",") + ")";
    }
    return cache.call(f, compExprs.mapA(function (e) return e.pos), macroId, printer);
    #else
    return cache.call(f, compExprs.mapToArray(function (e) return e.pos), macroId);
    #end
    
  }
  #end
  
  //} region private  
  
  //{ region public  
  
  /**
   * This is a Lambda Macro, it accepts functions in a compact lambda-like notation.
   * 
   * @example
   * 
   * F.n( a = a * 2 )                         // function (a) return a * 2
   * F.n( a,b = a * b )                       // function (a,b) return a * b
   * 
   * If you use an underscore for the binded variable you don't need to 
   * specify an argument (the use of underscore is by convention)
   * 
   * F.n( _ * 2)                              // function (_) return _ * 2
   * F.n( {varName:_,  2)                     // function (_) return _ * 2
   * 
   * or even
   * 
   * F.n( 2 )                                 // function (_) return 2
   * 
   * for explicit typing of arguments you have to pass the arguments as string, 
   * this is because haxe macros only allow valid haxe expressions passed to a macro
   * 
   * F.n( 'a:Int' = Std.string(a) )           // function (a:Int) return Std.string(a)
   * F.n( 'a:Int', 'b:Int' = Std.string(a + b) )  // function (a:Int, b:Int) return Std.string(a + b)
   * 
   * you can also mix typed and untyped variables:
   * 
   * F.n( 'a:Int', b = a + b )                // function (a:Int, b) return a + b
   * 
   * You can also define Blocks as the function body, in this case you have to use the return statement
   * if you want to return a value:
   * 
   * F.n( a = {var b = "hi " + a; return b})  // function (a) { var b = "hi" + a; return b }
   * 
   * 
   * @param  exprs  valid expressions as described above
   * @return        the generated function expression
   */
  @:macro public static function n(exprs:Array<Expr>):Expr 
  {
    #if (debug && Fdebug)
    var printer = function () {
      return "F.n(" + exprs.map(function (e) return EP.exprStr(e)) + ")";
    }
    return cache.call(function () return ShortLambda.make(exprs), exprs.map(function (e) return e.pos), "F.n" , printer);
    #else
    return cache.call(function () return ShortLambda.make(exprs), exprs.map(function (e) return e.pos), "F.n");
    #end
  }
  
  /**
   * Iterable/Iterator comprehension macro, the resulting container is an array.
   * 
   * Comprehension Syntax:
   * 
   * F.ac( outputfunction, generators [, generators]* )
   * generators := generator [&& generator]* [|| predicate]
   * generator := variable <= input
   * 
   * @example
   * 
   * F.ac(x + 2, x <= 0...10 || x % 2 == 0); // [2, 4, 6, 8, 10]
   *
   * You can combine multiple Input Iterables with the AND-Operator (&&).
   * 
   * F.ac({x:x, y:y}, x <= 0...5 && y <= 6...11); // [{x:0, y:6}, {x:1, y:7}, {x:2, y:8},{x:3, y:9},{x:4, y:10}]
   * 
   * var chars = ["a", "b", "c"]; 
   * var numbers = [2,3,4];
   * F.ac(c, c <= chars && n <= numbers || n != 3); // ["a","c"]
   * 
   * Take a look at http://code.google.com/p/scuts/wiki/The_F_Macro for more Examples.
   * 
   * @param  compExprs  At least two expressions where the first is the output function
   *                    and the second (and all following optional parameters) consists of 
   *                    one or more generator expression (separated by &&) and one optional 
   *                    predicate (separated by ||).
   * @return  An Array expression
   */
  @:macro public static function ac(compExprs:Array<Expr>):Expr 
  {
    
    var fact = function (ce) return CB.make(ce, CB.makeNewArrayExpr, CB.makeArrayAppendExpr);
    return buildComprehension(compExprs, fact, "F.ac");
  }
  /**
   * Iterable/Iterator comprehension macro, where the resulting container is an List.
   * 
   * Works in the same way as the array comprehension macro, but produces a list as the result.
   * 
   * Comprehension Syntax:
   * 
   * F.lc( outputfunction, generators [, generators]* )
   * generators := generator [&& generator]* [|| predicate]
   * generator := variable <= input
   * 
   * @example
   * 
   * F.ac(x + 2, x <= 0...10 || x % 2 == 0); // {2, 4, 6, 8, 10}
   * 
   * Take a look at http://code.google.com/p/scuts/wiki/The_F_Macro for more Examples.
   * 
   * @param  compExprs  At least two expressions where the first is the output function
   *                    and the second (and all following optional parameters) consists of 
   *                    one or more generator expression (separated by &&) and one optional 
   *                    predicate (separated by ||).
   * @return  A List expression
   */
  @:macro public static function lc(compExprs:Array<Expr>):Expr 
  {
    var fact = function (ce) return CB.make(ce, CB.makeNewListExpr, CB.makeListAppendExpr);
    return buildComprehension(compExprs, fact, "F.lc");
  }
  
  /**
   * Iterable/Iterator comprehension macro, where the resulting container is a lazy Iterator that
   * generates elements on demand.
   * 
   * Comprehension Syntax:
   * 
   * F.lazy( outputfunction, generators [, generators]* )
   * generators := generator [&& generator]* [|| predicate]
   * generator := variable <= input
   * 
   * @example
   * 
   * F.lazy(x + 2, x <= 0...10 || x % 2 == 0); // {2, 4, 6, 8, 10}
   * 
   * Take a look at http://code.google.com/p/scuts/wiki/The_F_Macro for more Examples.
   * 
   * @param  compExprs  At least two expressions where the first is the output function
   *                    and the second (and all following optional parameters) consists of 
   *                    one or more generator expression (separated by &&) and one optional 
   *                    predicate (separated by ||).
   * @return  An Iterator expression
   */
  
  @:macro public static function lazy(compExprs:Array<Expr>):Expr 
  {
    return buildComprehension(compExprs, CB.makeLazy, "F.lazyc");
  }
  
  //} region public  
}

